package com.basker.pisces.domain.validation.validator;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;

import com.basker.pisces.concurrent.lock.ILock;
import com.basker.pisces.concurrent.lock.ILockManager;
import com.basker.pisces.domain.command.event.CommandExecuteEvent;
import com.basker.pisces.domain.command.processor.ICommandExecuteProcessor;
import com.basker.pisces.domain.constant.DomainConstants.ObjectErrorCode;
import com.basker.pisces.domain.validation.ValidationErrors;
import com.basker.pisces.domain.validation.annotation.CommandValidator;
import com.basker.pisces.resource.StringResources;
import com.basker.pisces.service.exception.IObjectErrorsWrapper.ObjectError;
import com.basker.pisces.tx.TransactionHelper;

/**
 * 加锁即验证，如果没有成功获取锁则说明未通过校验，无论如何，最终会释放当前验证器拥有的锁
 *
 * @author hangwen
 */
@CommandValidator
public class LockValidator extends AbstractValidator implements ICommandExecuteProcessor {

    private static final String COMMAND_LOCK_KEY = "$" + LockValidator.class.getSimpleName();

    @Autowired
    private ILockManager lockManager;

    public void doUnLocks() {
        // 如果在事务内，那么须在事务完成后再释放锁，否则其他线程抢到锁后，依然可能读取到老数据
        if (TransactionHelper.inTransactionScope()) {
            TransactionHelper.doAfterCompletion(status -> this.doUnLocks0());
        } else {
            this.doUnLocks0();
        }
    }

    @Override
    public void processAfterInvoke(CommandExecuteEvent event) {
        this.doUnLocks();
    }

    @Override
    public ValidationErrors validate(Object[] pkValues) {
        if (this.getContext().getOption().isDisableLock()) {
            // option强制禁用锁
            return null;
        }

        @SuppressWarnings("unchecked")
        List<ILock> locks = (List<ILock>) this.getContext().getParameter(COMMAND_LOCK_KEY);
        if (locks != null) {
            // 已经加过锁了
            return null;
        }

        ValidationErrors errors = new ValidationErrors();

        locks = new ArrayList<>();
        boolean hasError = false;
        Integer waitTimeMills = this.getContext().getCommand().getLockWaitTimeMills();

        try {
            for (Object pkValue : pkValues) {
                Object id = pkValue;

                String lockKey = this.genLockKey(id);
                ILock lock = this.lockManager.getLock(lockKey);

                boolean lockSuccess = false;

                if (waitTimeMills <= 0) {
                    lockSuccess = lock.lock();
                } else {
                    lockSuccess = lock.lock(waitTimeMills, TimeUnit.MILLISECONDS);
                }

                if (lockSuccess) {
                    locks.add(lock);
                } else {
                    ObjectError error = createLockErrorById(id);
                    errors.addError(error);

                    hasError = true;
                    break;
                }
            }
        } finally {
            if (hasError) {
                // 释放已经占用的锁
                if (!locks.isEmpty()) {
                    for (ILock lock : locks) {
                        lock.unlock();
                    }
                }
            } else {
                this.getContext().addParameter(COMMAND_LOCK_KEY, locks);
            }
        }

        return errors;
    }

    private ObjectError createLockErrorById(Object id) {
        String errorMessage = StringResources.get("pisces-domain.LockValidator.failure",
                this.getEntity().getDisplayName() + ":" + id);

        return new ObjectError(ObjectErrorCode.OBJECT_COMMAND_LOCK_FAILURE, null, String.valueOf(id), errorMessage);
    }

    private void doUnLocks0() {
        if (this.getContext().getOption().isDisableLock()) {
            return;
        }

        @SuppressWarnings("unchecked")
        List<ILock> locks = (List<ILock>) this.getContext().getParameter(COMMAND_LOCK_KEY);

        if (locks != null) {
            for (ILock lock : locks) {
                lock.unlock();
            }

            this.getContext().removeParameter(COMMAND_LOCK_KEY);
        }
    }

    private String genLockKey(Object id) {
        return COMMAND_LOCK_KEY + ":" + this.getEntity().getFullEntityName() + ":" + id;
    }

}
